package bancosys.tec.services.input.driver.processor;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import jmine.tec.services.ServicesController;
import jmine.tec.services.annotations.ServiceFields;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceAssertionFailedError;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServiceProperties;
import jmine.tec.services.api.ServicesAPIMessages;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.execution.ServicesPageExecutor;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.services.api.factory.ServiceNotFoundException;
import jmine.tec.services.impl.ServicesImplMessages;
import jmine.tec.services.io.ServiceBean;
import jmine.tec.services.io.ServicesPageBean;

import org.apache.log4j.Logger;

import bancosys.tec.datadigester.DataDigesterException;
import bancosys.tec.datadigester.DigesterBean;
import bancosys.tec.datadigester.processor.Processor;
import bancosys.tec.persist.controller.PersistenceEnabledController;
import bancosys.tec.services.api.AbstractService;
import bancosys.tec.services.input.driver.bean.ServicePageHeader;

/**
 * Thread safe service processor.
 * 
 * @author lundberg
 */
public class ThreadSafeServiceProcessor implements Processor {
    private static final String FILE_SEPARATOR = System.getProperty("file.separator");

    private static final Logger LOG = Logger.getLogger(ThreadSafeServiceProcessor.class);

    private final ServicesController servicesController;

    private final ServicesPageExecutor servicesPageExecutor;

    /**
     * Construtor.
     * 
     * @param servicesController servicesController
     */
    public ThreadSafeServiceProcessor(ServicesController servicesController) {
        this(servicesController, servicesController.getServicesPageExecutor());
    }

    /**
     * Construtor.
     * 
     * @param servicesController servicesController
     * @param servicesPageExecutor servicesPageExecutor
     */
    public ThreadSafeServiceProcessor(ServicesController servicesController, ServicesPageExecutor servicesPageExecutor) {
        this.servicesController = servicesController;
        this.servicesPageExecutor = servicesPageExecutor;
    }

    /**
     * Faz o processamento do bean.
     * 
     * @param digesterBean bean que vai ser processado
     */
    public void process(DigesterBean digesterBean) {
        try {
            // Executar as servicesPages que foram processadas (instanciadas)
            this.servicesPageExecutor.execute(this.getServicesPages((ServicePageHeader) digesterBean));
        } catch (ServiceExecutionException e) {
            throw new DataDigesterException(ServicesImplMessages.ERROR_PROCESSING_SERVICES.create(), e);
        }
    }

    /**
     * Devolve a lista de ServicesPages.
     * 
     * @param header o bean de ServicePageHeader.
     * @return a lista de ServicesPages.
     */
    public List<ServicesPage> getServicesPages(ServicePageHeader header) {
        List<ServicesPage> servicesPages = new LinkedList<ServicesPage>();

        // Obter o nome do arquivo que gerou esse bean (se possivel)
        String filename = this.extractFilename(header);

        // As instancias de ServiceProperties necessitam de controllers para "recarregar"
        // objetos do banco de dados antes de de retornar o objeto para o servico.
        ServiceProperties parentServiceProperties = new ServiceProperties(this.servicesController);

        for (ServicesPageBean workSheet : header.getWorkSheets()) {
            if (workSheet == null) {
                // sanity check
                continue;
            }

            // Instanciar a servicesPage
            ServicesPage servicesPage = new ServicesPage();
            servicesPage.setName(workSheet.getName());

            for (ServiceBean bean : workSheet.getServices()) {
                if (bean == null) {
                    // sanity check
                    continue;
                }

                Map<String, Object> parameters = this.servicesController.instantiateMap();
                parameters.putAll(bean.getParameters());

                ServiceProperties serviceProperties = new ServiceProperties(this.servicesController, parentServiceProperties, parameters);
                Service<?> service = this.doInstantiateService(bean, filename, workSheet.getName(), serviceProperties);

                // Se conseguir instanciar o servico, nao adicionar para a pagina de servicos.
                if (service != null) {
                    servicesPage.addService(service);
                }
            }

            servicesPages.add(servicesPage);
        }
        return servicesPages;
    }

    /**
     * Cria uma nova instancia do servico definido pelo <b>serviceBean</b> com os parametros passados.
     * 
     * @param serviceBean serviceBean
     * @param fileName fileName
     * @param worksheetName worksheetName
     * @param serviceProperties serviceProperties
     * @return uma nova instancia do servico definido pelo bean ou <code>null</code> se nao for possivel instanciar o servico
     */
    private Service<?> doInstantiateService(ServiceBean serviceBean, String fileName, String worksheetName,
            ServiceProperties serviceProperties) {
        try {
            if (ActionsEnum.INVALIDA.equals(serviceBean.getAction())) {
                return new ErrorService(serviceBean, worksheetName, serviceProperties, this.servicesController);
            }

            Service<?> service =
                    this.servicesController.getServiceFactory().getService(serviceBean.getAction(), serviceBean.getName(),
                            serviceProperties);
            String serviceLocation = fileName;
            if (serviceBean.getServiceLocation() != null) {
                serviceLocation += serviceBean.getServiceLocation();
            }

            service.setServiceLocation(serviceLocation);

            return service;

        } catch (ServiceNotFoundException e) {
            LOG.error("Servico '" + serviceBean.getName() + "' nao encontrado (aba " + worksheetName + ", linha " + serviceBean.getRow()
                    + ")!", e);
            throw new DataDigesterException(ServicesImplMessages.ERROR_SERVICE_NOT_FOUND.create(serviceBean.getName(), worksheetName,
                    serviceBean.getRow()), e);
        } catch (ServiceCreationException e) {
            LOG.error(
                    "Erro durante a criacao do servico '" + serviceBean.getName() + ", Aba " + worksheetName + " linha "
                            + serviceBean.getRow(), e);
            throw new DataDigesterException(ServicesImplMessages.ERROR_SERVICE_CREATION_ERROR.create(serviceBean.getName(), worksheetName,
                    serviceBean.getRow()), e);
        }
    }

    /**
     * Extrai do bean {@link ServicePageHeader} o nome do arquivo que gerou esse bean.
     * 
     * @param servicePageHeader servicePageHeader
     * @return arquivo que gerou o {@link ServicePageHeader} ou "" se essa informacao nao foi fornecida pelo driver.
     */
    private String extractFilename(ServicePageHeader servicePageHeader) {
        String filename = "";

        if (servicePageHeader.getFileName() != null) {
            filename = servicePageHeader.getFileName();

            if (filename.lastIndexOf(FILE_SEPARATOR) != -1) {
                filename = filename.substring(filename.lastIndexOf(FILE_SEPARATOR) + 1);
            }
        }

        return filename;
    }

    /**
     * Serviço que representa um erro na leitura da planilha.
     * 
     * @created Nov 14, 2008
     * @author piercio (Nov 14, 2008)
     */
    @ServiceFields(action = ActionsEnum.INVALIDA, required = {})
    private class ErrorService extends AbstractService<PersistenceEnabledController> {

        private final ServiceBean bean;

        private final ServiceProperties properties;

        private final String worksheet;

        /**
         * Construtor.
         * 
         * @param bean o ServiceBean associado ao serviço em erro.
         * @param worksheetName nome da aba que contém o serviço.
         * @param properties as propriedades do serviço.
         * @param serviceController o controlador de serviços.
         */
        public ErrorService(ServiceBean bean, String worksheetName, ServiceProperties properties, ServicesController serviceController) {
            this.bean = bean;
            this.properties = properties;
            this.setServicesController(ThreadSafeServiceProcessor.this.servicesController);
            this.worksheet = worksheetName;
        }

        /**
         * Causa uma exceção ao ser executado, registrando assim no ExecutionLog
         */
        public void execute() {
            throw new ServiceAssertionFailedError(ServicesAPIMessages.IO_INVALID_ACTION_FOR_SERVICE.create(this.worksheet,
                    this.bean.getRow() + 1, this.bean.getComment()));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ActionsEnum getAction() {
            return this.bean.getAction();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getServiceName() {
            return this.bean.getComment();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getServiceLocation() {
            return this.bean.getServiceLocation();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ServiceProperties getServiceProperties() {
            return this.properties;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return this.bean.toString();
        }

    }
}
